Element Call einrichten: Verschlüsselte Videoanrufe mit Element X und Matrix Synapse

Seit April 2025 funktionieren Videoanrufe in Element X, Element Web und Element Desktop nicht mehr automatisch – zumindest dann nicht, wenn du deinen eigenen Matrix-Homeserver betreibst. Der Grund: Der bisher mitgelieferte, öffentlich gehostete LiveKit-Dienst von Element wurde eingestellt. Wer Anrufe nutzen möchte, muss das MatrixRTC-Backend nun selbst hosten.

Ein zentrales Element (im wahrsten Sinne) dieser Clients ist nämlich Element Call, das über das MatrixRTC-Backend läuft – inklusive LiveKit. Darüber werden Ende-zu-Ende-verschlüsselte (E2EE) Audio- und Videoanrufe direkt in der App ermöglicht – zuverlässig, mit hoher Qualität und auch über föderierte Server hinweg.

Element Call sorgt nicht nur für klassische 1:1-Gespräche, sondern unterstützt auch Gruppenanrufe, Bildschirmfreigabe, Reaktionen mit Emojis und eine stabile Verbindung – selbst bei größeren Konferenzen. Und genau dieses System musst du dir nun selbst aufsetzen.

Ich habe mein Setup deshalb um genau dieses Backend erweitert – bestehend aus LiveKit und einem kleinen JWT-Auth-Service, der die Verbindung absichert. Weil bei der Einrichtung ein paar Dinge beachtet werden müssen, habe ich alles Schritt für Schritt dokumentiert.

In diesem Beitrag findest du die komplette Einrichtung sowohl für Element X (als App) als auch für Element Web – damit Anrufe plattformübergreifend zuverlässig funktionieren.

Wichtig zu wissen:
Element (Legacy, auch Classic genannt) wird für MatrixRTC nicht mehr unterstützt. Diese ältere Version setzt noch auf Jitsi + TURN, was technisch nicht mehr dem aktuellen Stand entspricht und mittelfristig ausläuft. Was du in Zukunft brauchen wirst, ist Element X.
Damit nutzt du die aktuelle, von Matrix offiziell empfohlene Lösung für Audio- und Videoanrufe – vollständig selbst gehostet und direkt in dein bestehendes System integriert.

🔽 Element gibt’s für alle Plattformen – egal ob Android, iOS, Desktop oder Web.
Du findest alle offiziellen Downloads gebündelt unter: element.io/download

🤖 Was machen LiveKit und der JWT-Service eigentlich?

Bevor wir richtig loslegen, hier ein Überblick über die Komponenten, die du für dein eigenes Element Call Backend brauchst. Keine Sorge – vieles davon hast du wahrscheinlich schon im Einsatz oder lässt sich schnell nachrüsten.

Damit Element Call funktioniert, braucht es zwei zentrale Bausteine: LiveKit und den JWT-Service.

LiveKit ist für die gesamte Audio- und Videoverarbeitung zuständig. Es handelt sich dabei um eine sogenannte SFU (Selective Forwarding Unit), die eingehende Medienströme von Clients empfängt und gezielt an andere Teilnehmende weiterleitet. Das macht Gruppenanrufe effizient, ohne dass jeder Client mit jedem direkt verbunden sein muss. In unserem Setup läuft LiveKit als eigenständiger Docker-Container.

Der JWT-Service übernimmt die Authentifizierung. Er generiert sogenannte JSON Web Tokens (JWT), mit denen sich deine Matrix-Nutzer gegenüber LiveKit ausweisen. Ohne diesen Service würde LiveKit Verbindungen ablehnen – die Tokens sorgen also dafür, dass nur berechtigte Nutzer Zugriff erhalten.

Gemeinsam sorgen beide Dienste dafür, dass Audio- und Videoanrufe über Matrix sicher und zuverlässig ablaufen – selbst über föderierte Server hinweg.

Damit dein Element Call Backend aber reibungslos funktioniert, brauchst du neben den Docker-Containern noch ein paar ergänzende Einstellungen.

⚙️ Voraussetzungen

Bevor du loslegst, sollte deine Umgebung ein paar grundlegende Dinge mitbringen. Falls du schon länger mit Docker und Matrix arbeitest, hast du das meiste davon vermutlich bereits am Start:

  • Installierte Docker-Umgebung (inkl. Docker Compose)
  • Eingerichteter NGINX Webserver – z. B. mit dem NGINX Proxy Manager (andere Webserver gehen natürlich auch)
  • Konfigurierte Firewall, z. B. über UFW, idealerweise mit Kenntnis über dein internes Netzwerk

🚀 Was wir gemeinsam einrichten werden

In diesem Beitrag führe ich dich Schritt für Schritt durch die komplette Einrichtung deines MatrixRTC-Backends – damit Anrufe in Element X und Element Web funktionieren. Das erwartet dich:

  1. Docker-Container deployen – für LiveKit und JWT-Service
  2. LiveKit-Konfiguration anlegen – inklusive Medienports und Schlüssel
  3. Firewall konfigurieren – z. B. mit UFW, damit alles durchkommt
  4. DNS-Eintrag setzen – z. B. rtc.matrix.domain.com114.221.45.68
  5. NGINX Proxy einrichten – damit Anfragen an die richtigen Container gehen
  6. Well-Known-Konfiguration erstellen – damit Matrix-Clients dein Backend finden
  7. Synapse anpassen – aktivieren der experimentellen Features
  8. Element Web fit machen – Konfiguration für MatrixRTC im Webclient aktivieren

Wenn du diese Schritte einmal durchgehst, hast du am Ende dein eigenes, voll funktionsfähiges Element Call Backend – komplett selbst gehostet und bereit für sichere, moderne Sprach- und Videoanrufe über Matrix.

⚠️Hinweis:
Alle in diesem Beitrag gezeigten Tokens, Secrets, IPs und Domains wurden ausschließlich zu Dokumentationszwecken neu erzeugt. Sie spiegeln nicht die tatsächlichen Daten meines Produktivsystems wieder.

Also. los geht’s!

🔐 Schlüssel generieren (64 Zeichen)

Für die Kommunikation zwischen LiveKit und JWT-Service wird ein Value und ein Secret benötigt. Führe den Befehl also zwei Mal aus und schreibe dir das Value und das Secret irgendwo auf:

tr -dc 'a-zA-Z0-9' </dev/urandom | head -c 64

🐳 Docker Compose

Hier trägst du das Value und das Secret direkt ein.

services:
  matrix-element-call-jwt:
    image: ghcr.io/element-hq/lk-jwt-service:latest
    container_name: matrix-element-call-jwt
    hostname: matrix-element-call-jwt
    environment:
      - LK_JWT_PORT=8080
      - LIVEKIT_URL=https://rtc.matrix.domain.com/livekit/sfu
      - LIVEKIT_KEY=oLtjBM2NS3u8zU0zxzmktgswufzOQF5uNXVm2nuB9HuuCNRu03zvkaqniguotdxY
      - LIVEKIT_SECRET=zKgSeb0kvkdfgm68wfDXax3PcwwVaGCkRpSXKDeYy4ydTLbxRlUxKqWjZcQEfvqa
      - LIVEKIT_LOCAL_HOMESERVERS=domain.com
    networks:
      dockernet:
        ipv4_address: 172.16.0.181
    restart: unless-stopped
    ports:
      - :8080

  matrix-element-call-livekit:
    image: livekit/livekit-server:latest
    container_name: matrix-element-call-livekit
    hostname: matrix-element-call-livekit
    command: --dev --config /etc/livekit.yaml
    networks:
      dockernet:
        ipv4_address: 172.16.0.180
    ports:
      - :7880/tcp
      - 7881:7881/tcp
      - 7882:7882/tcp
      - 50100-50200:50100-50200/udp
    restart: unless-stopped
    volumes:
      - ./data/matrix-element-call-livekit/config.yaml:/etc/livekit.yaml:ro

networks:
  dockernet:
    external: true

Ein kleiner Hinweis zur folgenden Umgebungsvariable:

- LIVEKIT_LOCAL_HOMESERVERS=domain.com

Diese Einstellung ist Teil einer noch nicht final veröffentlichten Funktion von LiveKit. Sie sorgt künftig dafür, dass nur Benutzer deines eigenen Homeservers neue Anrufe starten dürfen. Nutzer von föderierten Homeservern können sich weiterhin in bestehende Anrufe einklinken – aber keine neuen erstellen.

Das ist vor allem dann nützlich, wenn du deinen Dienst öffentlich anbietest, aber die Kontrolle darüber behalten möchtest, wer tatsächlich Anrufe initiieren darf. Bis die Funktion offiziell aktiv ist, hat dieser Eintrag noch keine Auswirkung – du kannst ihn aber bereits setzen, um vorbereitet zu sein.

So erlaubst du mehreren Homeservern das Initiieren von Anrufen:

- LIVEKIT_LOCAL_HOMESERVERS=domain.com,example.com

📝 LiveKit-Konfiguration anlegen

mkdir -p ./data/matrix-element-call-livekit && nano ./data/matrix-element-call-livekit/config.yaml

Inhalt:

port: 7880
bind_addresses:
  - "0.0.0.0"
rtc:
  tcp_port: 7881
  port_range_start: 50100
  port_range_end: 50200
  use_external_ip: false
  ips:
    includes:
      - 114.221.45.68/32
logging:
  level: debug
turn:
  enabled: false
  domain: localhost
  cert_file: ""
  key_file: ""
  tls_port: 5349
  udp_port: 443
  external_tls: true
keys:
  oLtjBM2NS3u8zU0zxzmktgswufzOQF5uNXVm2nuB9HuuCNRu03zvkaqniguotdxY: "zKgSeb0kvkdfgm68wfDXax3PcwwVaGCkRpSXKDeYy4ydTLbxRlUxKqWjZcQEfvqa"

Weitere Einstellungen kannst du dem Konfigurationsbeispiel aus dem Github Repository entnehmen.

Das Value und das Secret, welche du vorhin erzeugt hast, werden hier ebenfalls eingetragen.

🔍 Hinweis zur Netzwerkeinstellung

Standardmäßig empfiehlt LiveKit, den Wert use_external_ip auf true zu setzen. In diesem Fall kann der Abschnitt ips: komplett entfallen, da LiveKit die externe IP automatisch ermittelt.

In meinem Setup hat das allerdings nicht zuverlässig funktioniert, da ich mit einem Docker-Bridge-Netzwerk hinter einer Firewall arbeite. Daher musste ich use_external_ip: false setzen und meine öffentliche IP explizit über ips.includes eintragen.

Welche Variante bei dir funktioniert, hängt stark von deiner Umgebung ab – insbesondere davon, wie Docker und die Netzwerkstruktur aufgebaut sind. Am besten testest du beide Varianten und beobachtest das Verhalten beim Verbindungsaufbau.

🌐 Docker-Netzwerk erstellen (optional)

docker network create --subnet=172.16.0.0/24 dockernet

Wenn das Docker Netzwerk nicht erstellt wird, ist drauf zu achten, dass der Teil in der Docker-Compose auch rausgenommen wird.

🔥 Firewall konfigurieren – Beispiel mit UFW

Je nach Setup und Hosting-Umgebung wird die Netzwerksicherheit unterschiedlich gehandhabt. Manche nutzen eine vorgeschaltete Cloud-Firewall (z. B. bei Hetzner oder Ionos), andere – so wie ich – setzen direkt auf dem VPS eine lokale Firewall ein, beispielsweise mit UFW (Uncomplicated Firewall).

Da ich in meinem Setup Docker-Container in einem eigenen Subnetz verwende, habe ich zusätzlich docker-ufw im Einsatz, um gezielt Zugriffe zwischen Host und Containern zu regeln.

Hier ein Beispiel, wie du das mit UFW umsetzen kannst:

sudo ufw route allow proto tcp from any to 172.16.0.180 port 7881 comment "Matrix LiveKit TCP"
sudo ufw route allow proto tcp from any to 172.16.0.180 port 7882 comment "Matrix LiveKit TCP"
sudo ufw route allow proto udp from any to 172.16.0.180 port 50100:50200 comment "Matrix LiveKit UDP Media Range"

Diese Regeln erlauben eingehenden Verkehr auf die relevanten Ports im internen Docker-Netzwerk – in diesem Fall für den LiveKit-Dienst.

🚪 Zusätzliche Ports für den Zugriff von außen

Wenn du einen Reverse Proxy wie NGINX Proxy Manager verwendest, achte darauf, dass die Standard-HTTP(S)-Ports offen sind:

  • Port 80 (HTTP)
  • Port 443 (HTTPS)

Der NGINX Proxy Manager selbst ist im Browser typischerweise unter Port 81 erreichbar – das ist nur für die Verwaltungsschnittstelle relevant und muss nicht öffentlich freigegeben werden, solange du lokal arbeitest.

🧭 DNS-Eintrag setzen

Ein A-Record reicht aus:

rtc.matrix	7200	IN	A	114.221.45.68

🔄 NGINX Proxy Manager: Weiterleitungen einrichten

Die Weiterleitungen für den JWT-Service und LiveKit legst du im Bereich „Advanced“ der Subdomain-Konfiguration im NGINX Proxy Manager an.

Dabei gilt es einen wichtigen Punkt zu beachten:

⚠️ Hinweis zu Zertifikaten und Subdomains

Wenn du eine Subdomain wie rtc.matrix.domain.com verwendest, kannst du kein Wildcard-Zertifikat für *.domain.com verwenden – dieses deckt nur eine Ebene ab (also z. B. matrix.domain.com, mail.domain.com etc.).
Subdomains mit mehr als einer Ebene, wie rtc.matrix.domain.com, fallen nicht unter ein einfaches Wildcard-Zertifikat.

✅ Lösungsmöglichkeiten:

  • Ändere die Subdomain, z. B. zu rtc-matrix.domain.com – das funktioniert mit *.domain.com
  • Oder: Erzeuge ein separates Let’s Encrypt-Zertifikat für rtc.matrix.domain.com direkt über den NGINX Proxy Manager

Warum das so ist?
Wildcard-Zertifikate wie *.domain.com decken nur eine Subdomain-Ebene ab. Für tiefer verschachtelte Domains (*.matrix.domain.com) bräuchtest du ein eigenes Wildcard-Zertifikat für diese Struktur – was mit Let’s Encrypt nicht ohne weiteres geht.

location ^~ /livekit/jwt/ {
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_pass http://172.16.0.181:8080/;
}

location ^~ /livekit/sfu/ {
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_send_timeout 120;
  proxy_read_timeout 120;
  proxy_buffering off;
  proxy_set_header Accept-Encoding gzip;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_pass http://172.16.0.180:7880/;
}

Denke bitte daran, das Netzwerkprotokoll „Websocket“ zu aktivieren.

🧩 Well-Known-Konfiguration

Ein wichtiger Teil der Einrichtung ist der .well-known-Eintrag. Er sagt Matrix-Clients wie ElementX, wo dein Homeserver erreichbar ist – und zusätzlich, wie sie dein LiveKit-Backend für Anrufe nutzen können.
Dabei ist entscheidend, auf welcher Domain dieser Eintrag bereitgestellt wird – und das hängt direkt vom server_name ab, den du in deiner homeserver.yaml konfiguriert hast.

🔍 So bestimmst du die richtige Domain

Wenn dein Matrix-Server in der homeserver.yaml den Eintrag
server_name: domain.com
enthält,
→ dann muss der .well-known-Eintrag auf der Hauptdomain https://domain.com liegen.

Wenn dein server_name stattdessen
server_name: matrix.domain.com,
lautet,
→ dann gehört der Eintrag natürlich auf die Subdomain https://matrix.domain.com.

location /.well-known/matrix/client {
  default_type application/json;
  add_header Content-Type application/json;
  add_header "Access-Control-Allow-Origin" *;
  return 200 '{"m.homeserver": {"base_url": "https://matrix.domain.com"}, "m.identity_server": {"base_url": "https://vector.im"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://rtc.matrix.domain.com/livekit/jwt"}, {"type": "nextgen_new_foci_type", "props_for_nextgen_foci": "val"}]}';
}

Wenn du dir nicht sicher bist, auf welche Domain der Eintrag gehört: Schaue in deiner homeserver.yaml unter server_name: – diese Angabe bestimmt die Ziel-Domain für den .well-known-Pfad.

Sobald deine Konfiguration aktiv ist, kannst du den .well-known-Pfad ganz einfach testen – direkt über die Kommandozeile:

curl https://domain.com/.well-known/matrix/client

Die Ausgabe sollte in etwa so aussehen:

{"m.homeserver": {"base_url": "https://matrix.domain.com"}, "m.identity_server": {"base_url": "https://vector.im"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://rtc.matrix.domain.com/livekit/jwt"}, {"type": "nextgen_new_foci_type", "props_for_nextgen_foci": "val"}]}

Wenn du eine solche Rückgabe bekommst, ist alles richtig eingerichtet. Andernfalls prüfe bitte die Domain, die JSON-Syntax und ob dein Webserver die Datei korrekt ausliefert.

🛠️ Homeserver.yaml erweitern

Für Synapse müssen ein paar experimentelle Features aktiviert bzw. ergänzt werden:

experimental_features:
  msc3266_enabled: true
  msc4222_enabled: true
  msc4140_enabled: true

max_event_delay_duration: 24h

rc_message:
  per_second: 0.5
  burst_count: 30

rc_delayed_event_mgmt:
  per_second: 1
  burst_count: 20

Danach einmal die Services (neu)starten – das sollte es eigentlich gewesen sein.

🧪 Element Web fit machen für MatrixRTC

Wenn du Element Web verwendest und dort ebenfalls direkt über MatrixRTC telefonieren möchtest, musst du eine kleine zusätzliche Konfiguration vornehmen.
Standardmäßig nutzt Element Web nämlich noch den klassischen „Legacy“-Anrufweg – also Jitsi in Kombination mit TURN. Das funktioniert zwar auch, vorausgesetzt, es ist konfiguriert, ist aber technisch nicht so tief in Matrix integriert und fühlt sich eher wie ein externer Anrufdienst an.

Seit einiger Zeit unterstützt Element Web jedoch nativ das MatrixRTC-Backend mit LiveKit und JWT – also genau das Setup, das du in dieser Anleitung eingerichtet hast. Damit das auch im Web-Client aktiv wird, musst du in der Konfigurationsdatei von Element bestimmte Optionen setzen.

⚙️ Konfiguration in Config.json

Öffne die Datei config.json deiner Element Web-Instanz und ergänze (oder passe) folgenden Block an:

"features": {
    "feature_video_rooms": true,
    "feature_group_calls": true,
    "feature_element_call_video_rooms": true
}

📁 Die Datei liegt bei einem Docker-Setup meist im Verzeichnis /app/config.json oder je nach Containerstruktur an anderer Stelle.

🎥 Wo finde ich die neue Anruffunktion?

Nach dem Neustart findest du im Chat-Fenster (oben rechts) einen Kamera-Button.
Wenn du diesen klickst, öffnet sich ein Dialog – hier kannst du auswählen:

  • Legacy-Anruf (Jitsi, TURN-basiert)
  • Element Call (Beta) – das ist dein neues MatrixRTC-gestütztes LiveKit-Backend 🎉

✅ Fazit

Mit ein paar gezielten Schritten lässt sich das eigene MatrixRTC-Backend zuverlässig betreiben – dank Docker, LiveKit, JWT-Service und einem sauber eingerichteten Proxy. Ich hoffe, diese Anleitung hilft dir beim Einrichten deines eigenen Element Call Backends und erspart dir ein bisschen Sucherei.

An dieser Stelle möchte ich mich bei der Matrix-Community bedanken, insbesondere bei der Gruppe
👉 #webrtc:matrix.org,
die mir bei einigen Fragen schnell und freundlich weitergeholfen hat.

Wenn du dich ebenfalls mit dem Thema beschäftigst, lohnt sich ein Blick dorthin auf jeden Fall!

Falls du noch Fragen hast oder irgendwo hängst, schreib mir gerne über die Kommentarfunktion – ich schaue regelmäßig rein.
Oder schau einfach direkt in meine Matrix-Community vorbei:
👉 #community:techniverse.net

Dort tauschen wir uns zu Matrix, Selfhosting und allem Drumherum aus – du bist herzlich willkommen!

Ich wünsche dir viel Erfolg bei der Einrichtung!

📑 Ähnliche Beiträge
Migration von MySQL 5.7 zu MySQL 8.0 in Docker

Der Wechsel von MySQL 5.7 auf die neuere Version 8.0 kann auf den ersten Blick einschüchternd wirken. Besonders wenn du Read more

Vergrößern einer Partition in Ubuntu

Stell dir vor, deine Ubuntu-Installation braucht einfach mehr „Luft zum Atmen“. Angenommen, du hast eine 100GB-SSD, auf der 30GB für Read more

Asciinema 3: Installation der neuesten Version des Terminal Session Recorder

Asciinema hat sich als das Tool der Wahl für das Aufzeichnen und Teilen von Terminal-Sessions etabliert. In der kommenden Version Read more

Rustdesk-Server als Dockerlösung

Warum RustDesk? Sicherheitsvorfälle bei zentralisierten Fernwartungslösungen wie AnyDesk und TeamViewer zeigen, wie wichtig unabhängige Alternativen sind. Im Februar 2024 bestätigte Read more

Fritz!Box per Bash-Script neustarten

Heute möchte ich dir ein nützliches Bash-Script vorstellen, das mir persönlich sehr geholfen hat. Es handelt sich um ein "Fritz!Box Read more

Vielen Dank fürs Teilen!